/* Copyright Andy Mitchell 2007. No warranties */

function ThreadListUI(gtdinboxPage){
try{
	this.eventObservers = new EventObservers(this);
	this.tlEventObservers = new EventObservers(this);
	this.gtdinboxPage = gtdinboxPage;
	this.doc = gtdinboxPage.doc;
	this._threadList = gtdinboxPage._threadList;
	this._labels = gtdinboxPage._labels;
	//this._threadListPopupUI = gtdinboxPage._threadListPopupUI;
	this._refreshQueue = [];
	this._autoLabelSeen = {};
	this._multiToOne = new MultiToOne(gtdinboxPage, 100);
	
	this._labels.addEventHandler( this, "actionlist-manual-click", this.manualThreadLabelChange );
}catch(e){
	if( GTDDEBUGMODE ) alert("threadlistui constructor\n" + e.toString());
}
};

ThreadListUI.prototype.viewChange = function(gmail){
	// Add a column; and update each row.
	
	/*
	var result = this.doc.evaluate( "//table[@class='N2mZ7b']//tbody", elTL, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null );
	var elTable = result.singleNodeValue;
	*/

	if( this.listenDOM() ) this.listenDOM(false);
	this.elTL = this.gtdinboxPage.gmailDOM.element("ActiveViewElement", gmail);
	this.listenDOM(true);	

	this.refresh();
};

ThreadListUI.prototype.listenDOM = function(v){
	if( typeof v=="undefined" ) return this._listen;
	if( v ){
		if( !this._listen ) this.eventObservers.observe(this.elTL, "DOMNodeInserted", this.domNodeInserted, false);
	}else{
		if( this._listen ) this.eventObservers.stopObserving(this.elTL, "DOMNodeInserted", this.domNodeInserted, false);
	}
	this._listen = v;
};
ThreadListUI.prototype.domNodeInserted = function(event){
	this._multiToOne.process("threadListDomChange", GIPROTOTYPE.bind(this, this.refresh));
};


ThreadListUI.prototype.refresh = function(callback, forceProcess){
	this.listenDOM(false);
	
	if( !this.elTL ) return;
	
	if( !callback ) this._refreshQueue.push( {'elTL':this.elTL, 'forceProcess':(forceProcess||null)} );
	if( this._isRunning || this._refreshQueue.length==0 ) return;

	this.tlEventObservers.reset();
	this.tlEventObservers.observe( this.elTL, "mousedown", this.mouseDown, true);
	this.tlEventObservers.observe( this.elTL, "contextmenu", this.contextMenu, true);

	var elTL = this._refreshQueue[0].elTL;
	forceProcess = this._refreshQueue[0].forceProcess;
	this._refreshQueue.splice(0,1);
		
	try{
	this._isRunning = true;
	var autoLabel = {count:0, checked:this.checkRows()};
	
	var view = this.gtdinboxPage.view();
	
	var elImg = this.doc.createElement("IMG");
	elImg.className = "gtd-row-button";
	elImg.style.position = "absolute";
	elImg.style.right = "0px";
	elImg.src = "chrome://gtdinbox/content/gtd_lineitem.gif";
	elImg.setAttribute("onmouseover", "this.src='chrome://gtdinbox/content/gtd_lineitem-hover.gif'");
	elImg.setAttribute("onmouseout", "this.src='chrome://gtdinbox/content/gtd_lineitem.gif'");
	var result = this.gtdinboxPage.gmailDOM.elements("threadlist-rows-lastcol"); 
	this.rowCount = result.snapshotLength;
	var tryAutoLabelAll = this.gtdinboxPage.gmailDOM.test("is-sent-mail");
	var repeatRefresh = false;
	if( result.snapshotLength>0 ){
		if( forceProcess || (result.snapshotItem(0).innerHTML.indexOf("gtd")==-1 || result.snapshotItem(result.snapshotLength-1).innerHTML.indexOf("gtd")==-1) ){
			for( var i = 0; i < result.snapshotLength; i++ ){
				var elImgC = elImg.cloneNode(true);
				if( !forceProcess || result.snapshotItem(i).innerHTML.indexOf("gtd")==-1 ){
					result.snapshotItem(i).appendChild( elImgC );
				}
				/*
					var elRow = result.snapshotItem(i).parentNode;
					if( tryAutoLabelAll || this.gtdinboxPage.gmailDOM.test("thread-row-unread", elRow) ){
						var row = this.parseRow(elRow);
						var thread = row.thread();
						if( !thread ){	// bad, means didn't load properly. should always be a thread. issue another refresh.
							repeatRefresh = true;
						}
						if( thread && !this._autoLabelSeen[thread.id] ){
							this._autoLabelSeen[thread.id] = true;
							var labels = thread.getBodyLabels(true);
							for( var j = 0; j < labels.length; j++ ){
								var elCheck = row.elCheck;
								var labelname = labels[j];
								var threadid = thread.id;
								autoLabel.count++;
								var fPreAdd = GIPROTOTYPE.bind(this, function(){
										this.checkRows([]);
										this.checkRow( elCheck, true );
									});
								var fCallback = GIPROTOTYPE.bind(this, function(act){
										if( --autoLabel.count==0 ){	// last one is done
											this.checkRows(autoLabel.checked);
										}
									});
								this.gtdinboxPage._labels.labelThread({'labelname':labelname, on:true, threads:thread, 'fPreAdd':fPreAdd, 'fCallback':fCallback});
							}
						}
					}
				*/
 			}
		}
	}
	
	}catch(e){if( GTDDEBUGMODE ) alert("threadlistui refresh\n" + e.toString())}
	this._isRunning = false;
	
	if( this._refreshQueue.length > 0 ){
		setTimeout( GIPROTOTYPE.bind(this, function(){this.refresh(true)}), 50);
	}
	
	
	if( this.gtdinboxPage._threadListPopupUI.poppedUp() ){	// Precipitate a refresh; used for Create Label (create label causes row update)	
		var elRows = this.getCheckedRows();
		if( elRows.length > 0 ){
			var row = this.parseRow(elRows[0].elRow);
			this.gtdinboxPage._threadListPopupUI.setRow( row );
		}
		/*
		setTimeout( GIPROTOTYPE.bind(this, function(){	// TODO Would rather not a crude timeout; but, when popupui reads thread.verifyLabels( getLabels() ), getLabels is wrong (does not have new label)
			var elRows = this.getCheckedRows();
			if( elRows.length > 0 ){
				var row = this.parseRow(elRows[0].elRow);
				this.gtdinboxPage.unsafeWindow.console.log("refresh");
				this.gtdinboxPage._threadListPopupUI.setRow( row );
			}
		}), 100);
		*/
	}
	
	if( !forceProcess && repeatRefresh ){
		setTimeout( GIPROTOTYPE.bind(this, function(){
			this.refresh(null, true);
		}), 3000);
	}
	
	this.listenDOM(true);
};


ThreadListUI.prototype.checkRow = function(elCheck, v){
	if( (v && !elCheck.checked) || (!v && elCheck.checked) ){	// invert selection. must use keypress
		GIPROTOTYPE.doMouseEvent( this.doc, elCheck, "click");
	}
};
ThreadListUI.prototype.checkRows = function(a, update){
	// Get or set the status of the rows. 
	var result = this.gtdinboxPage.gmailDOM.elements("threadlist-rows-checkbox");
	if( typeof a=="undefined" || !a ){	// return array of checked items
		var a = [];
		for( var i = 0; i < result.snapshotLength; i++ ){
			a[i] = result.snapshotItem(i).checked;
			if( typeof update!="undefined" ) this.checkRow(result.snapshotItem(i), update);
		}
		return a;
	}else{	// check items
		for( var i = 0; i < result.snapshotLength; i++ ){
			this.checkRow(result.snapshotItem(i), (a[i] || false));
		}
	}
};
ThreadListUI.prototype.getCheckedRows = function(){
	var rows = [];
	//var result = this.doc.evaluate( "//div[@gtdviewid='" + this.gtdinboxPage.view("gtdviewid") + "']//table[@class='N2mZ7b']//tr[starts-with(@class,'xweT7d AnqB9d rfza3e')]//td//input[@type='checkbox']", this.elTL, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
	var result = this.gtdinboxPage.gmailDOM.elements("threadlist-rows-checked-checkbox");
	for( var i = 0; i < result.snapshotLength; i++ ){
		if( result.snapshotItem(i).checked ){
			var row = this.parseRow(result.snapshotItem(i).parentNode.parentNode);
			rows.push( row );
		}
	}
	return rows;
};
ThreadListUI.prototype.uncheckOutOfView = function(){
	// Items that remain checked, even when not in view; affect the 'More Actions' drop down
	// Uncheck all checkboxes not currently in view
	// BEWARE: can cause problems on sent items. Checking sent item causes inbox variant to be checked; and unchecking outofview inbox variant consequently unchecks the sent item row.
try{
	var viewid = this.gtdinboxPage.view("gtdviewid");
	//var result = this.doc.evaluate( "//table[@class='N2mZ7b']//tr[starts-with(@class,'xweT7d AnqB9d rfza3e')]//td//input[@type='checkbox']", this.elTL, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
	var result = this.gtdinboxPage.gmailDOM.elements("threadlist-rows-checked-checkbox");
	for( var i = 0; i < result.snapshotLength; i++ ){
		var el = result.snapshotItem(i);
		if( el.checked ){
			var inview = false;
			while( el.parentNode && el.getAttribute ){
				if( el.getAttribute("gtdviewid")==viewid ){
					inview = true;
					break;
				}
				el = el.parentNode;
			}
			if( !inview ){
				this.checkRow( result.snapshotItem(i), false );
			}
		}
	}
}catch(e){
	if( GTDDEBUGMODE ) alert("threadlistui uncheckOutOfView\n" + e.toString());
}
};

ThreadListUI.prototype.getRowByThread = function(thread){
try{
	// Scan all rows for a possible match to this thread (row to contain subset of subject, names, snippet).
	//	If possible candidate, reverse lookup the row to see if it returns the thread
	//	If so, return that row
	
	
	var result = this.gtdinboxPage.gmailDOM.elements("threadlist-allrows");
	for( var i = 0; i < result.snapshotLength; i++ ){
		var row = new ThreadRow(this, result.snapshotItem(i));
		

		if( thread.testSubject(row.subject) ){
			if( thread.testNames(row.names) ){
				if( thread.testSnippet(row.snippet) ){
					return row;
				}
			}
		}
		
	}	
}catch(e){
	if( GTDDEBUGMODE ) alert("threadlistui getRowByThread\n" + e.toString());
}
};

ThreadListUI.prototype.parseRow = function(elRow){
	try{
		/*
		var row = {};
		row.elRow = elRow;
		var result = this.gtdinboxPage.gmailDOM.parseElement("thread-list-row", elRow);
		GIPROTOTYPE.extend( row, result );
		
		row.thread = GIPROTOTYPE.bind(this, function(noCheck, debug){
							var thread = this._threadList.getThreadByRow( row.subject, row.snippet, row.names )
				if( thread ){
					return thread;
				}else if(!noCheck){	// Try to retrieve my taking an adjacent, more easily identified, row; and exploiting Gmail's chronological nature to work back/forward to find the first match with these credentials
					var adjRow = null;
					var usedNext = true;
					if( row.elRow.nextSibling && row.elRow.nextSibling.nodeName=="TR" ){
						adjRow = this.parseRow(row.elRow.nextSibling);
					}else{
						usedNext = false;
						adjRow = this.parseRow(row.elRow.previousSibling);
					}
					thread = adjRow.thread(true);
					if( thread ){
						var fNext = function(t){
								return usedNext? t.previous() : t.next();
							};
						while(thread = fNext(thread)){
							if( thread.testNames(row.names) && thread.testSnippet(row.snippet) ) return thread;
						}
					}
				}
			});
		*/
		var row = new ThreadRow(this, elRow);
		return row;
	}catch(e){
		if( GTDDEBUGMODE ) alert("threadlistui parseRow\n" + e.toString());
	}
};

function ThreadRow(threadListUI, elRow){
	this._threadListUI = threadListUI;
	this._gtdinboxPage = threadListUI.gtdinboxPage;
	this.elRow = elRow;
	
	var result = this._gtdinboxPage.gmailDOM.parseElement("thread-list-row", elRow);
	GIPROTOTYPE.extend( this, result );
};
ThreadRow.prototype.thread = function(noCheck, debug){
		var thread = this._threadListUI._threadList.getThreadByRow( this.subject, this.snippet, this.names )
		if( thread ){
			return thread;
		}else if(!noCheck){	// Try to retrieve my taking an adjacent, more easily identified, row; and exploiting Gmail's chronological nature to work back/forward to find the first match with these credentials
			var adjRow = null;
			var usedNext = true;
			if( this.elRow.nextSibling && this.elRow.nextSibling.nodeName=="TR" ){
				adjRow = this._threadListUI.parseRow(this.elRow.nextSibling);
			}else if( this.elRow.previousSibling && this.elRow.previousSibling.nodeName=="TR" ){
				usedNext = false;
				adjRow = this._threadListUI.parseRow(this.elRow.previousSibling);
			}
			if( adjRow ){
				thread = adjRow.thread(true);
				if( thread ){
					var fNext = function(t){
							return usedNext? t.previous() : t.next();
						};
					var i = 0;
					while(thread = fNext(thread)){
						if( i++>50 ) return;
						if( thread.testNames(this.names) && thread.testSnippet(this.snippet) ) return thread;
					}
				}
			}
		}
};


ThreadListUI.prototype.mouseDown = function(event){
	try{
		var el = GIPROTOTYPE.element(event);
		
		if( this.gtdinboxPage.settings.labels.starForNextAction && this.gtdinboxPage.gmailDOM.test("star", el) ){	// It's a star
			// Ensure el points at the row:
			var gmailDOM = this.gtdinboxPage.gmailDOM;
			var elRow = el;
			while( !gmailDOM.test("thread-row", elRow) && elRow.nodeName!="BODY" ) elRow = elRow.parentNode;
			if( !gmailDOM.test("thread-row", elRow) ) return;
			var row = this.parseRow(elRow);
			var thread = row.thread();
			// Check row:
			this.uncheckOutOfView();
			var checkedRows = this.checkRows(null, false);
			this.checkRow(row.elCheck, true);
			// Test to see if star is on/off and respond
			var settings = this.gtdinboxPage.settings;
			var labelNextAction = settings.labels.types.Statuses.prefix + settings.labels.types.Statuses.defaults.NextAction;
			var labelsCount = {i:0};
			var fCallback = GIPROTOTYPE.bind(this, function(){
				if( --labelsCount.i==0 ){
					this.checkRows(checkedRows);	// restore
				}
			});
			if( !this.gtdinboxPage.gmailDOM.test("star-on", el) ){	// its on, add label (note: inverted due to event timing & icon updating)
				var statusPrefix = settings.labels.types.Statuses.prefix;
				if( settings.labels.onlyOneStatus  ){	// It's a status, there can only be one!
					for( var i = 0; i < thread.labels.length; i++ ){
						if( thread.labels[i].indexOf(statusPrefix)==0 ){
							labelsCount.i++;
							this.gtdinboxPage._labels.labelThread({'labelname':thread.labels[i], 'on':false, 'threads':thread, 'fCallback':fCallback});
						}
					}
				}
				labelsCount.i++;
				this.gtdinboxPage._labels.labelThread({labelname:labelNextAction, 'on':true, 'threads':thread, 'fCallback':fCallback});
			}else{	// Off, make sure it's off
				labelsCount.i++;
				this.gtdinboxPage._labels.labelThread({labelname:labelNextAction, 'on':false, 'threads':thread, 'fCallback':fCallback});
			}
			return;
		}
		
		if( event.button==0 && el.className=="gtd-row-button" ){
			GIPROTOTYPE.stop(event);
			this.popupRow(el.parentNode.parentNode, el);
		}else if( event.button==2 ){
			GIPROTOTYPE.stop(event);
		}
	}catch(e){
		if( GTDDEBUGMODE ) alert("threadlistui mouse down\n" + e.toString());
	}
};
ThreadListUI.prototype.contextMenu = function(event){
	try{
		var el = GIPROTOTYPE.element(event);
		
		// Ensure el points at the row:
		var gmailDOM = this.gtdinboxPage.gmailDOM;
		while( !gmailDOM.test("thread-row", el) && el.nodeName!="BODY" ) el = el.parentNode;
		if( !gmailDOM.test("thread-row", el) ) return;
		
		GIPROTOTYPE.stop(event);
		this.popupRow(el, event, {location: "bottomLeft", margin: 15} );
	}catch(e){
		if( GTDDEBUGMODE ) alert("threadlistui context menu\n" + e.toString());
	}
};

ThreadListUI.prototype.popupRow = function(elRow, popupType, popupOptions){
	this.gtdinboxPage._threadListPopupUI.popupOff();	// clear out checked rows
		
	var row = this.parseRow(elRow);
	this.gtdinboxPage._threadListPopupUI.popup( popupType, popupOptions );
	this.uncheckOutOfView();
	this.checkRow(row.elCheck, true);
	
	setTimeout( GIPROTOTYPE.bind(this, function(){
		this.gtdinboxPage._threadListPopupUI.setRow( row );
		}), 5 );
	//this.gtdinboxPage._threadListPopupUI.setRow( row );	// Something to do with the stack originating from the context-event, and later hitting a .apply, was causing heavy cyclical problems
};


ThreadListUI.prototype._manualThreadLabelChangeRow = function(label, rows, originallyCheckedRows, statusFinished, onlyOneStatus){
try{
	if( rows.length==0 ){
		if( statusFinished==label.safelabel ){	// we should have archived; so no need to restore
			this.checkRows(null, false);
		}else{
			this.checkRows(originallyCheckedRows);
		}
		return;
	}
	var row = rows[0];
	rows.splice(0,1);
	var thread = row.thread();
	this.checkRows(null, false);	// uncheck all
	this.checkRow(row.elCheck, true);	// check current one
	var fActiveLabelsCallback = function(activeLabels){
			try{
			thread.verifyLabels( activeLabels, true );	// ensure verify labels does not remove statuses
			var labelled = {count:0};
			if( statusFinished==label.safelabel || onlyOneStatus ){	// remove non-current status labels	
				var fCallback = GIPROTOTYPE.bind(this, function(act){
					try{
					if( --labelled.count==0 ){	// move on
						if( statusFinished==label.safelabel ){
							var threadActionsUI = new ThreadActionsUI(this.gtdinboxPage, null, true);
							threadActionsUI._doMarkReadAndArchive( GIPROTOTYPE.bind(this, function(){this._manualThreadLabelChangeRow(label, rows, originallyCheckedRows, statusFinished, onlyOneStatus)}) );
						}else{
							this._manualThreadLabelChangeRow(label, rows, originallyCheckedRows, statusFinished, onlyOneStatus);
						}
					}
					}catch(e){
						if( GTDDEBUGMODE ) alert("ThreadListUI _manuThreadLabelChangeRow fActiveLabelsCallback fCallback\n" + e.toString());
					}
				});
				for( var i = 0; i < thread.labels.length; i++ ){
					if( thread.labels[i].indexOf(this.gtdinboxPage.settings.labels.types.Statuses.prefix)==0 && thread.labels[i]!=label.safelabel ){
						labelled.count++;
						this._labels.labelThread({labelname:thread.labels[i], 'on':false, 'threads':thread, 'fCallback':fCallback});
					}
				}
			}else{
				this._manualThreadLabelChangeRow(label, rows, originallyCheckedRows, statusFinished, onlyOneStatus);
			}
			}catch(e){
				if( GTDDEBUGMODE ) alert("ThreadListUI _manuThreadLabelChangeRow fActiveLabelsCallback\n" + e.toString());
			}
		};
	this._labels.getActiveLabels(this, fActiveLabelsCallback)
}catch(e){
	if( GTDDEBUGMODE ) alert("ThreadListUI _manualThreadLabelChangeRow\n" + e.toString());
}
};
ThreadListUI.prototype.manualThreadLabelChange = function(event, label){
	// Called when user manually selects label on drop down
	// Kept seperate from Labels because ThreadList/Conversation would handle it differently

try{
	if( this.gtdinboxPage.view("view")!="tl" ) return;	// i should be active view
	
	var rows = this.getCheckedRows();	
	if( rows.length==0 ) return;
		
	//if( !label.safelabel.indexOf( this.gtdinboxPage.settings.labels.types.Projects.prefix )==0 ) return;
	
	
	var statusPrefix = this.gtdinboxPage.settings.labels.types.Statuses.prefix;
	if( label.act=="12" && label.safelabel.indexOf( statusPrefix )==0 ){
		// It's a status label; and rules may apply that dictate only one status can exist at once
		// The problem is that theres no guarentee each selected row's thread object is synced with the labels on that item
		// So, need to process each selected row, and verify labels
		// If Finished, remove all non-finished labels then mark as read & archive
		// If only one status, remove all non-finished labels
		var f = GIPROTOTYPE.bind(this, function(){
				this.uncheckOutOfView();
				var checked = this.checkRows(null, false);
				var statusFinished = statusPrefix + this.gtdinboxPage.settings.labels.types.Statuses.defaults.Finished;
				var onlyOneStatus = this.gtdinboxPage.settings.labels.onlyOneStatus;
				this._manualThreadLabelChangeRow(label, rows, checked, statusFinished, onlyOneStatus);
			});
		setTimeout(f, 200);
	}else{
		// Process like normal, syncing the thread objects for each
		for( var i = 0; i < rows.length; i++ ){
			var thread = rows[i].thread();
			if( thread ){
				if( label.act=="12" ){
					var f = function(){					
						thread.addLabel(label.label);
					}
					setTimeout(f, 200);
				}else{
					thread.removeLabel(label.label);
				}
			}
		}
	}
	
	
}catch(e){
	if( GTDDEBUGMODE ) alert("threadlistui manualThreadLabelChange\n" + e.toString());
}
};

ThreadListUI.prototype.destroy = function(){
	this._isRunning = true;
	this._refreshQueue.length = 0;
	this.eventObservers.destroy();
	this.tlEventObservers.destroy();
	this._multiToOne.destroy();
};